﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Xml.Linq;
using MyVocabulary.App_Code;
using MyVocabulary.Popup;

namespace MyVocabulary
{
    public partial class Verbs : Form
    {
        #region Consts

        protected static class FormMessages
        {
            public const string FormTitle = "Verbs File - {0}";

            public const string ErrorMessageTitle = "Error";
            public const string FileNotFound = "The verbs file could not be fould.";
            public const string ErrorLoadingFile = "An error occured while loading the verbs file. Details: {0}.";
            public const string ErrorSavingFile = "An error occured while saving the file. Details: {0}.";
            public const string ErrorExportingFile = "An error occured while exporting the list to a .txt file. Details: {0}.";

            public const string WindowClosingTitle = "Window Close Confirmation";
            public const string ConfirmWindowClosingQuestion =
                "There are unsaved modifications that will be lost if you close the window. Do you want to close it anyway?";

            public const string ReloadFormTitle = "Reload";
            public const string ConfirmReloadQuestion =
                "Doing a reload will cause any unsaved data to be lost. Are you sure you want to reload the file?";

            public const string SaveSuccessTitle = "Save";
            public const string SaveSuccessMsg = "The file was saved successfully.";

            public const string MultipleSelectionForEdit = "Please select only one item to edit.";

            public const string DeleteFormTitle = "Delete";
            public const string ConfirmItemRemovalQuestion =
                "Do you really want to remove the selected item(s) from the list?";

            public const string StartTestError = "Can not start the test until you enter at least one verb in the list.";

            public const string HtmlFileStructure = @"
                <!doctype html>
                <html>
                <head>
                    <meta http-equiv=""content-type"" content=""text/html; charset=UTF-8"" />
                    <title>My Vocabulary Verbs Export</title>
                    <style type=""text/css"">
                        table.tbl
                        {
                            border-width: 1px;
                            border-spacing: 0px;
                            border-style: solid;
                            border-color: gray;
                            border-collapse: collapse;
                        }
                        table.tbl td
                        {
                            border-width: 1px;
                            padding: 2px;
                            border-style: solid;
                            border-color: gray;
                        }                       
                        .infinitiveForm
                        {
        	                background-color: Navy;
        	                color : White;
        	                font-weight :bold;
                            text-align : center;
                        }
                        .conjugation
                        {
        	                background-color: #dddddd;
        	                color : Black;
        	                font-weight :bold;
                            text-align : center;
                        }   
                        table.noBorder
                        {
                            border-width: 0px;
                            border-spacing: 0px;
                            border-style: none;            
                            border-collapse: collapse;
                        }
                        table.noBorder td
                        {
                            border-width: 0px;
                            padding: 2px;
                            border-style: none;
                        }   
                        .highlightedText     
                        {   
        	                color: Navy;
                        }
                    </style>
                </head>
                <body>
                    {BODY_CONTENT}
                </body>
                </html>";
        }

        protected static class PersonBottomMenuTooltips
        {
            public const string Add = "Create a new item under the selected element in the list or at the end of the list if no selection is available";
            public const string Edit = "Edit the selected grammatical person";
            public const string Delete = "Delete the selected grammatical person(s)";
        }

        protected static class VerbsBottomMenuTooltips
        {
            public const string Add = "Create a new item under the selected element in the list or at the end of the list if no selection is available";
            public const string Edit = "Edit the selected verb";
            public const string Delete = "Delete the selected verb(s)";
        }

        #endregion

        #region Fields & Properties

        /// <summary>
        /// The path to the file that contains the verbs to show.
        /// </summary>
        public string FilePath { get; set; }

        /// <summary>
        /// The name of the file that contains the verbs to show will be used in the form title.
        /// </summary>
        public string FileName { set { Text = string.Format(FormMessages.FormTitle, value); } }

       /// <summary>
        /// Flag used to see if the user modified the data that was initially loaded / last saved
       /// </summary>
        protected bool hasDataChanges;

        protected bool errorLoadingFile;
        /// <summary>
        /// True if an error occured while loading the file
        /// </summary>
        public bool ErrorLoadingFile { get { return errorLoadingFile; } }
        
        /// <summary>
        /// Contains all the items loaded from the file (gramatical persons and verbs)
        /// </summary>
        protected VocabularyVerb verbsFile;

        #endregion

        #region Constructors

        /// <summary>
        /// Create the form
        /// </summary>
        /// <param name="FilePath">The path to the file that contains the verbs to show.</param>
        public Verbs(string FilePath)
        {            
            InitializeComponent();

            this.FilePath = FilePath;
            LoadFile();

            LoadPersonsListView();
            LoadVerbsListView();
        }

        /// <summary>
        /// Read the file located at the specified FilePath and load the data into the verbsFile field.
        /// </summary>
        protected void LoadFile()
        {
            errorLoadingFile = false;           
            hasDataChanges = false;
            verbsFile = new VocabularyVerb {Persons = new List<Person>(), Verbs = new List<Verb>()};

            //Check if the file exists
            if (!File.Exists(FilePath))
            {
                new CustomMessageBox(FormMessages.FileNotFound, FormMessages.ErrorMessageTitle, CustomMessageBoxButtons.Cancel).ShowDialog(this);
                errorLoadingFile = true;
                return;
            }

            //Read the file
            try
            {
                var doc = XDocument.Load(FilePath);
                var element = doc.Root;
                if (element == null || !element.HasElements)
                    return;

                #region Read the persons
                
                foreach (var e in element.Elements(VocabularyVerbConst.Persons))
                {
                    foreach (var pers in e.Elements(VocabularyVerbConst.Person))
                    {
                        var text = pers.Element(VocabularyVerbConst.PersonText);
                        var translation = pers.Element(VocabularyVerbConst.PersonTranslation);

                        if (text != null && translation != null && !string.IsNullOrEmpty(text.Value.Trim()) && !string.IsNullOrEmpty(translation.Value.Trim()))
                            verbsFile.Persons.Add(new Person {Text = text.Value, Translation = translation.Value});
                    }
                }

                #endregion

                #region Read the verbs
                
                foreach (var e in element.Elements(VocabularyVerbConst.Verbs))
                {
                    foreach (var verb in e.Elements(VocabularyVerbConst.Verb))
                    {
                        var newVerb = new Verb {Conjugations = new List<VerbConjugation>()};
                        foreach (var atr in verb.Elements())
                        {
                            switch (atr.Name.LocalName)
                            {
                                case VocabularyVerbConst.InfinitiveForm:
                                    newVerb.InfinitiveForm = atr.Value;
                                    break;
                                case VocabularyVerbConst.Translation:
                                    newVerb.Translation = atr.Value;
                                    break;
                                case VocabularyVerbConst.Examples:
                                    newVerb.Examples = atr.Value;
                                    break;
                                case VocabularyVerbConst.Remarks:
                                    newVerb.Remarks = atr.Value;
                                    break;
                                case VocabularyVerbConst.Conjugations:                                   
                                    foreach (var conjug in atr.Elements(VocabularyVerbConst.VerbConjugation))
                                    {                                        
                                        var node = conjug.Element(VocabularyVerbConst.Tense);

                                        if(node == null || string.IsNullOrEmpty(node.Value.Trim()))
                                            continue;

                                        var examles = conjug.Element(VocabularyVerbConst.VerbConjugationExamples);
                                        var remarks = conjug.Element(VocabularyVerbConst.VerbConjugationRemarks);
                                        var newConjugation = new VerbConjugation
                                                                 {
                                                                     Tense = node.Value,
                                                                     Examples = examles == null ? string.Empty : examles.Value,
                                                                     Remarks = remarks == null ? string.Empty : remarks.Value,
                                                                     Conjugation = new List<KeyValuePair<string, string>>()
                                                                 };

                                        foreach (var items in conjug.Elements(VocabularyVerbConst.Conjugation))
                                        {
                                            foreach (var item in items.Elements(VocabularyVerbConst.ConjugationItem))
                                            {
                                                node = item.Element(VocabularyVerbConst.ConjugationKey);
                                                if(node != null && !string.IsNullOrEmpty(node.Value.Trim()))
                                                {
                                                    var value = item.Element(VocabularyVerbConst.ConjugationValue);
                                                    newConjugation.Conjugation.Add(
                                                        new KeyValuePair<string, string>(node.Value, value == null
                                                                                                         ? string.Empty
                                                                                                         : value.Value));                                                    
                                                }
                                            }
                                        }

                                        newVerb.Conjugations.Add(newConjugation);
                                    }
                                    break;
                            }
                        }

                        if(!string.IsNullOrEmpty(newVerb.InfinitiveForm.Trim()))
                            verbsFile.Verbs.Add(newVerb);
                    }
                }

                #endregion
            }
            catch (Exception exp)
            {
                new CustomMessageBox(string.Format(FormMessages.ErrorLoadingFile, exp.Message), FormMessages.ErrorMessageTitle, CustomMessageBoxButtons.Cancel).ShowDialog(this);
                errorLoadingFile = true;
                return;
            }
        }

        #endregion

        #region Grammatical Persons

        /// <summary>
        /// Load the data into the persons ListView
        /// </summary>
        protected void LoadPersonsListView()
        {
            lvPersons.Items.Clear();
            foreach (var person in verbsFile.Persons)
            {
                var item = new CustomListViewItem(new[] {person.Text, person.Translation});
                item.AdditionalInfo[VocabularyVerbConst.Person] = person;
                lvPersons.Items.Add(item);
            }
            SetPersonsMenuButtons();
        }

        private void lvPersons_SelectedIndexChanged(object sender, EventArgs e)
        {
            SetPersonsMenuButtons();
        }

        private void lvPersons_DoubleClick(object sender, EventArgs e)
        {
            btnPersonEdit_Click(sender, e);
        }

        private void cmPersons_Opening(object sender, System.ComponentModel.CancelEventArgs e)
        {
            cmPersons.Items["btnPersonEdit"].Visible =
                cmPersons.Items["btnPersonDelete"].Visible = lvPersons.SelectedItems.Count > 0;
        }

        #region Grammatical persons bottom menu buttons
        
        /// <summary>
        /// Set the buttons that appear at the buttom of the grammatical persons ListView
        /// </summary>
        protected void SetPersonsMenuButtons()
        {
            if (lvPersons.SelectedItems.Count == 0)
            {
                btnPersonMenu_1.Visible = true;
                SetAddPersonButton(btnPersonMenu_1);

                btnPersonMenu_2.Visible = false;

                btnPersonMenu_3.Visible = false;              

                return;
            }

            btnPersonMenu_1.Visible = true;
            SetDeletePersonButton(btnPersonMenu_1);

            btnPersonMenu_2.Visible = true;
            SetEditPersonButton(btnPersonMenu_2);

            btnPersonMenu_3.Visible = true;
            SetAddPersonButton(btnPersonMenu_3);
            
        }

        protected void SetAddPersonButton(Button btn)
        {
            btn.Image = Properties.Resources.Add;
            toolTip1.SetToolTip(btn, PersonBottomMenuTooltips.Add);
            SetPersonOnClick(btn, btnPersonAdd_Click);
        }

        protected void SetEditPersonButton(Button btn)
        {
            btn.Image = Properties.Resources.Edit;
            toolTip1.SetToolTip(btn, PersonBottomMenuTooltips.Edit);
            SetPersonOnClick(btn, btnPersonEdit_Click);
        }

        protected void SetDeletePersonButton(Button btn)
        {
            btn.Image = Properties.Resources.Delete;
            toolTip1.SetToolTip(btn, PersonBottomMenuTooltips.Delete);
            SetPersonOnClick(btn, btnPersonDelete_Click);
        }
       
        protected void SetPersonOnClick(Button btn, EventHandler method)
        {
            btn.Click -= btnPersonAdd_Click;
            btn.Click -= btnPersonEdit_Click;
            btn.Click -= btnPersonDelete_Click;

            btn.Click += method;
        }

        #endregion

        #region Grammatical person commands

        private void btnPersonAdd_Click(object sender, EventArgs e)
        {
            var form = new AddGrammaticalPerson(AddGrammaticalPerson.AddGrammaticalPersonMode.Add, OnPersonDataSaved);
            form.ShowDialog(this);
        }

        private void btnPersonEdit_Click(object sender, EventArgs e)
        {          
            if(lvPersons.SelectedItems.Count == 0)
                return;

            if (lvPersons.SelectedItems.Count > 1)
            {
                (new CustomMessageBox(FormMessages.MultipleSelectionForEdit, FormMessages.ErrorMessageTitle,
                                      CustomMessageBoxButtons.Cancel)).ShowDialog();
                return;
            }

            var item = (CustomListViewItem) lvPersons.SelectedItems[0];
            var person = (Person)item.AdditionalInfo[VocabularyVerbConst.Person];

            var form = new AddGrammaticalPerson(AddGrammaticalPerson.AddGrammaticalPersonMode.Edit, OnPersonDataSaved, person);
            form.ShowDialog(this);
        }

        public void OnPersonDataSaved(object sender, EventArgs e)
        {
            if (sender == null || !(sender is AddGrammaticalPerson))
                return;

            var form = sender as AddGrammaticalPerson;            
            switch (form.Mode)
            {
                case AddGrammaticalPerson.AddGrammaticalPersonMode.Add:                    
                    var item = new CustomListViewItem(new[] { form.VocabPerson.Text, form.VocabPerson.Translation });
                    item.AdditionalInfo[VocabularyVerbConst.Person] = form.VocabPerson;
                    if (lvPersons.SelectedItems.Count == 0)
                        lvPersons.Items.Add(item); //Add on the last position
                    else
                        lvPersons.Items.Insert(lvPersons.SelectedItems[0].Index + 1, item); //Add after the first selected item
                    item.EnsureVisible();
                    break;
                case AddGrammaticalPerson.AddGrammaticalPersonMode.Edit:
                    var editItem = (CustomListViewItem)lvPersons.SelectedItems[0];
                    editItem.SubItems[0].Text = form.VocabPerson.Text;
                    editItem.SubItems[1].Text = form.VocabPerson.Translation;
                    editItem.AdditionalInfo[VocabularyVerbConst.Person] = form.VocabPerson;
                    break;                    
            }
            //Mark the data as modified
            hasDataChanges = true;
        }

        private void btnPersonDelete_Click(object sender, EventArgs e)
        {
            if (lvPersons.SelectedItems.Count == 0)
                return;

            if ((new CustomMessageBox(FormMessages.ConfirmItemRemovalQuestion, FormMessages.DeleteFormTitle, CustomMessageBoxButtons.YesNo)).ShowDialog() == DialogResult.Yes)
            {
                foreach (ListViewItem item in lvPersons.SelectedItems)
                {
                    lvPersons.Items.Remove(item);
                }

                //Mark the data as modified
                hasDataChanges = true;
            }
        }

        #endregion

        #region Drag&Drop

        private void lvPersons_ItemDrag(object sender, ItemDragEventArgs e)
        {
            lvPersons.DoDragDrop("lvPersons", DragDropEffects.Move);
        }

        private void lvPersons_DragEnter(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(DataFormats.Text))
            {
                e.Effect = DragDropEffects.None;
                return;
            }
            var text = (string)e.Data.GetData(typeof(string));
            e.Effect = text.CompareTo("lvPersons") == 0 ? DragDropEffects.Move : DragDropEffects.None;
        }

        private void lvPersons_DragOver(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(DataFormats.Text))
            {
                e.Effect = DragDropEffects.None;
                return;
            }
            var cp = lvPersons.PointToClient(new Point(e.X, e.Y));
            var hoverItem = lvPersons.GetItemAt(cp.X, cp.Y);
            if (hoverItem == null)
            {
                e.Effect = DragDropEffects.None;
                return;
            }
            foreach (CustomListViewItem moveItem in lvPersons.SelectedItems)
            {
                if (moveItem.Index == hoverItem.Index)
                {
                    e.Effect = DragDropEffects.None;
                    hoverItem.EnsureVisible();
                    return;
                }
            }
            var text = (String)e.Data.GetData("lvPersons".GetType());
            if (text.CompareTo("lvPersons") == 0)
            {
                e.Effect = DragDropEffects.Move;
                hoverItem.EnsureVisible();
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        private void lvPersons_DragDrop(object sender, DragEventArgs e)
        {
            if (lvPersons.SelectedItems.Count == 0)
            {
                return;
            }
            var cp = lvPersons.PointToClient(new Point(e.X, e.Y));
            var dragToItem = lvPersons.GetItemAt(cp.X, cp.Y);
            if (dragToItem == null)
            {
                return;
            }
            var dropIndex = dragToItem.Index;
            if (dropIndex > lvPersons.SelectedItems[0].Index)
            {
                dropIndex++;
            }
            var insertItems = new ArrayList(lvPersons.SelectedItems.Count);
            foreach (CustomListViewItem item in lvPersons.SelectedItems)
            {
                var newItem = (CustomListViewItem)item.Clone();
                newItem.AdditionalInfo = item.AdditionalInfo;
                insertItems.Add(newItem);
            }
            for (var i = insertItems.Count - 1; i >= 0; i--)
            {
                var insertItem = (CustomListViewItem)insertItems[i];
                lvPersons.Items.Insert(dropIndex, insertItem);
            }
            foreach (CustomListViewItem removeItem in lvPersons.SelectedItems)
            {
                lvPersons.Items.Remove(removeItem);
            }
        }

        #endregion

        #endregion

        #region Verbs

        /// <summary>
        /// Load the data into the verbs ListView
        /// </summary>
        protected void LoadVerbsListView()
        {
            lvVerbs.Items.Clear();
            foreach (var verb in verbsFile.Verbs)
            {
                var item = new CustomListViewItem(new[] { verb.InfinitiveForm, verb.Translation });
                item.AdditionalInfo[VocabularyVerbConst.Verb] = verb;
                lvVerbs.Items.Add(item);
            }
            SetVerbsMenuButtons();

            btnTest.Enabled = btnSort.Enabled = btnSortDesc.Enabled = (lvVerbs.Items.Count > 0);
        }

        private void lvVerbs_SelectedIndexChanged(object sender, EventArgs e)
        {
            SetVerbsMenuButtons();
        }

        private void lvVerbs_DoubleClick(object sender, EventArgs e)
        {
            btnVerbEdit_Click(sender, e);
        }

        private void cmVerbs_Opening(object sender, System.ComponentModel.CancelEventArgs e)
        {
            cmVerbs.Items["btnVerbEdit"].Visible =
                cmVerbs.Items["btnVerbDelete"].Visible = lvVerbs.SelectedItems.Count > 0;
        }

        #region Verbs list bottom menu buttons
        
        /// <summary>
        /// Set the buttons that appear at the buttom of the verbs ListView
        /// </summary>
        protected void SetVerbsMenuButtons()
        {
            if (lvVerbs.SelectedItems.Count == 0)
            {
                btnVerbsMenu_1.Visible = true;
                SetAddVerbButton(btnVerbsMenu_1);

                btnVerbsMenu_2.Visible = false;

                btnVerbsMenu_3.Visible = false;

                return;
            }

            btnVerbsMenu_1.Visible = true;
            SetDeleteVerbButton(btnVerbsMenu_1);

            btnVerbsMenu_2.Visible = true;
            SetEditVerbButton(btnVerbsMenu_2);

            btnVerbsMenu_3.Visible = true;
            SetAddVerbButton(btnVerbsMenu_3);
        }

        protected void SetAddVerbButton(Button btn)
        {
            btn.Image = Properties.Resources.Add;
            toolTip1.SetToolTip(btn, VerbsBottomMenuTooltips.Add);
            SetVerbOnClick(btn, btnVerbAdd_Click);
        }

        protected void SetEditVerbButton(Button btn)
        {
            btn.Image = Properties.Resources.Edit;
            toolTip1.SetToolTip(btn, VerbsBottomMenuTooltips.Edit);
            SetVerbOnClick(btn, btnVerbEdit_Click);
        }

        protected void SetDeleteVerbButton(Button btn)
        {
            btn.Image = Properties.Resources.Delete;
            toolTip1.SetToolTip(btn, VerbsBottomMenuTooltips.Delete);
            SetVerbOnClick(btn, btnVerbDelete_Click);
        }

        protected void SetVerbOnClick(Button btn, EventHandler method)
        {
            btn.Click -= btnVerbAdd_Click;
            btn.Click -= btnVerbEdit_Click;
            btn.Click -= btnVerbDelete_Click;

            btn.Click += method;
        }

        #endregion

        #region Verbs Commands

        private void btnVerbAdd_Click(object sender, EventArgs e)
        {
            var form = new AddVerb(AddVerb.AddVerbWindowMode.Add, OnVerbDataSaved, GetPersonsList(), GetExistingTensesList());
            form.ShowDialog(this);
        }

        private void btnVerbEdit_Click(object sender, EventArgs e)
        {
            if (lvVerbs.SelectedItems.Count == 0)
                return;

            if (lvVerbs.SelectedItems.Count > 1)
            {
                (new CustomMessageBox(FormMessages.MultipleSelectionForEdit, FormMessages.ErrorMessageTitle,
                                      CustomMessageBoxButtons.Cancel)).ShowDialog();
                return;
            }

            var item = (CustomListViewItem)lvVerbs.SelectedItems[0];
            var verb = (Verb)item.AdditionalInfo[VocabularyVerbConst.Verb];

            var form = new AddVerb(AddVerb.AddVerbWindowMode.Edit, OnVerbDataSaved, GetPersonsList(), GetExistingTensesList(), verb);
            form.ShowDialog(this);
        }

        public void OnVerbDataSaved(object sender, EventArgs e)
        {
            if (sender == null || !(sender is AddVerb))
                return;

            var form = sender as AddVerb;
            switch (form.Mode)
            {
                case AddVerb.AddVerbWindowMode.Add:
                    var item = new CustomListViewItem(new[] { form.VocabVerb.InfinitiveForm, form.VocabVerb.Translation });
                    item.AdditionalInfo[VocabularyVerbConst.Verb] = form.VocabVerb;
                    if (lvVerbs.SelectedItems.Count == 0)
                        lvVerbs.Items.Add(item); //Add on the last position
                    else
                        lvVerbs.Items.Insert(lvVerbs.SelectedItems[0].Index + 1, item); //Add after the first selected item
                    item.EnsureVisible();
                    break;
                case AddVerb.AddVerbWindowMode.Edit:
                    var editItem = (CustomListViewItem)lvVerbs.SelectedItems[0];
                    editItem.SubItems[0].Text = form.VocabVerb.InfinitiveForm;
                    editItem.SubItems[1].Text = form.VocabVerb.Translation;
                    editItem.AdditionalInfo[VocabularyVerbConst.Verb] = form.VocabVerb;
                    break;
            }

            //Enable the test if the list has at least one verb
            btnTest.Enabled = btnSort.Enabled = btnSortDesc.Enabled = lvVerbs.Items.Count > 0;

            //Mark the data as modified
            hasDataChanges = true;
        }

        private void btnVerbDelete_Click(object sender, EventArgs e)
        {
            if (lvVerbs.SelectedItems.Count == 0)
                return;

            if ((new CustomMessageBox(FormMessages.ConfirmItemRemovalQuestion, FormMessages.DeleteFormTitle, CustomMessageBoxButtons.YesNo)).ShowDialog() == DialogResult.Yes)
            {
                foreach (ListViewItem item in lvVerbs.SelectedItems)
                {
                    lvVerbs.Items.Remove(item);
                }

                //Enable the test if the list has at least one verb
                btnTest.Enabled = btnSort.Enabled = btnSortDesc.Enabled = lvVerbs.Items.Count > 0;

                //Mark the data as modified
                hasDataChanges = true;
            }
        }

        /// <summary>
        /// Get the list of grammatical persons to use by default when adding new verb conjugations
        /// </summary>
        /// <returns></returns>
        protected List<Person> GetPersonsList()
        {
            var persons = new List<Person>();
            foreach(CustomListViewItem item in lvPersons.Items)
            {
                persons.Add((Person)item.AdditionalInfo[VocabularyVerbConst.Person]);
            }
            return persons;
        }

        /// <summary>
        /// Get the existing tenses that will be used as suggestions when adding new verb conjugations
        /// </summary>
        protected List<string> GetExistingTensesList()
        {
            var tenses = new List<string>();
            foreach (CustomListViewItem item in lvVerbs.Items)
            {
                var verb = (Verb) item.AdditionalInfo[VocabularyVerbConst.Verb];
                foreach(var conjugation in verb.Conjugations)
                {
                    if (!tenses.Contains(conjugation.Tense))
                        tenses.Add(conjugation.Tense);
                }                
            }
            return tenses;
        }

        #endregion

        #region Drag&Drop

        private void lvVerbs_ItemDrag(object sender, ItemDragEventArgs e)
        {
            lvVerbs.DoDragDrop("lvVerbs", DragDropEffects.Move);
        }

        private void lvVerbs_DragEnter(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(DataFormats.Text))
            {
                e.Effect = DragDropEffects.None;
                return;
            }
            var text = (string)e.Data.GetData(typeof(string));
            e.Effect = text.CompareTo("lvVerbs") == 0 ? DragDropEffects.Move : DragDropEffects.None;
        }

        private void lvVerbs_DragOver(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(DataFormats.Text))
            {
                e.Effect = DragDropEffects.None;
                return;
            }
            var cp = lvVerbs.PointToClient(new Point(e.X, e.Y));
            var hoverItem = lvVerbs.GetItemAt(cp.X, cp.Y);
            if (hoverItem == null)
            {
                e.Effect = DragDropEffects.None;
                return;
            }
            foreach (CustomListViewItem moveItem in lvVerbs.SelectedItems)
            {
                if (moveItem.Index == hoverItem.Index)
                {
                    e.Effect = DragDropEffects.None;
                    hoverItem.EnsureVisible();
                    return;
                }
            }
            var text = (String)e.Data.GetData("lvVerbs".GetType());
            if (text.CompareTo("lvVerbs") == 0)
            {
                e.Effect = DragDropEffects.Move;
                hoverItem.EnsureVisible();
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        private void lvVerbs_DragDrop(object sender, DragEventArgs e)
        {
            if (lvVerbs.SelectedItems.Count == 0)
            {
                return;
            }            
            var cp = lvVerbs.PointToClient(new Point(e.X, e.Y));
            var dragToItem = lvVerbs.GetItemAt(cp.X, cp.Y);
            if (dragToItem == null)
            {
                return;
            }
            var dropIndex = dragToItem.Index;
            if (dropIndex > lvVerbs.SelectedItems[0].Index)
            {
                dropIndex++;
            }
            var insertItems = new ArrayList(lvVerbs.SelectedItems.Count);
            foreach (CustomListViewItem item in lvVerbs.SelectedItems)
            {
                var newItem = (CustomListViewItem)item.Clone();
                newItem.AdditionalInfo = item.AdditionalInfo;
                insertItems.Add(newItem);
            }
            for (var i = insertItems.Count - 1; i >= 0; i--)
            {
                var insertItem = (CustomListViewItem)insertItems[i];
                lvVerbs.Items.Insert(dropIndex, insertItem);
            }
            foreach (CustomListViewItem removeItem in lvVerbs.SelectedItems)
            {
                lvVerbs.Items.Remove(removeItem);
            }
        }

        #endregion

        #endregion

        #region Form buttons & Events
        
        /// <summary>
        /// Enable form shortcuts:
        /// * Ctrl + S - Save the form
        /// * Ctrl + F - Search
        /// * Ctrl + T - Start the test
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Verbs_KeyDown(object sender, KeyEventArgs e)
        {
            if (!e.Control) return;

            switch (e.KeyCode)
            {
                case Keys.S:
                    e.Handled = true;
                    e.SuppressKeyPress = true;
                    btnSave_Click(sender, e);
                    break;
                case Keys.F:
                    e.Handled = true;
                    e.SuppressKeyPress = true;
                    tbSearch.SelectAll();
                    tbSearch.Focus();
                    break;
                case Keys.T:
                    e.Handled = true;
                    e.SuppressKeyPress = true;
                    btnTest_Click(sender, e);
                    break;
            }
        }

        /// <summary>
        /// If the form contains unsaved changes prompt the user before closing it.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Verbs_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (!hasDataChanges)
                return;

            if ((new CustomMessageBox(FormMessages.ConfirmWindowClosingQuestion, FormMessages.WindowClosingTitle, CustomMessageBoxButtons.YesNo)).ShowDialog() == DialogResult.No)
            {
                e.Cancel = true;
            }
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            try
            {
                var doc = XDocument.Load(FilePath);
                var element = doc.Root;
                if (element == null)
                {
                    element = new XElement("DocumentElement");
                    doc.AddFirst(element);
                }

                element.Elements().Remove();

                #region Persons

                var personsElement = new XElement(VocabularyVerbConst.Persons);
                foreach (CustomListViewItem item in lvPersons.Items)
                {
                    var data = (Person)item.AdditionalInfo[VocabularyVerbConst.Person];
                    var person = new XElement(VocabularyVerbConst.Person);
                    person.Add(new XElement(VocabularyVerbConst.PersonText, data.Text));
                    person.Add(new XElement(VocabularyVerbConst.PersonTranslation, data.Translation));
                    personsElement.Add(person);
                }
                element.Add(personsElement);

                #endregion

                #region Verbs

                var verbsElement = new XElement(VocabularyVerbConst.Verbs);
                foreach (CustomListViewItem item in lvVerbs.Items)
                {
                    var data = (Verb)item.AdditionalInfo[VocabularyVerbConst.Verb];
                    var verb = new XElement(VocabularyVerbConst.Verb);
                    verb.Add(new XElement(VocabularyVerbConst.InfinitiveForm, data.InfinitiveForm));
                    verb.Add(new XElement(VocabularyVerbConst.Translation, data.Translation));
                    verb.Add(new XElement(VocabularyVerbConst.Examples, data.Examples));
                    verb.Add(new XElement(VocabularyVerbConst.Remarks, data.Remarks));
                    
                    //Conjugations
                    var conjugations = new XElement(VocabularyVerbConst.Conjugations);
                    foreach (var c in data.Conjugations)
                    {
                        var verbConjugation = new XElement(VocabularyVerbConst.VerbConjugation);
                        verbConjugation.Add(new XElement(VocabularyVerbConst.Tense, c.Tense));
                        verbConjugation.Add(new XElement(VocabularyVerbConst.VerbConjugationExamples, c.Examples));
                        verbConjugation.Add(new XElement(VocabularyVerbConst.VerbConjugationRemarks, c.Remarks));
                        var conjugation = new XElement(VocabularyVerbConst.Conjugation);
                        foreach (var pair in c.Conjugation)
                        {
                            var conjugationItem = new XElement(VocabularyVerbConst.ConjugationItem);
                            conjugationItem.Add(new XElement(VocabularyVerbConst.ConjugationKey, pair.Key));
                            conjugationItem.Add(new XElement(VocabularyVerbConst.ConjugationValue, pair.Value));
                            conjugation.Add(conjugationItem);
                        }
                        verbConjugation.Add(conjugation);
                        conjugations.Add(verbConjugation);
                    }
                    verb.Add(conjugations);

                    verbsElement.Add(verb);
                }
                element.Add(verbsElement);

                #endregion

                //Save the modifications
                doc.Save(FilePath);

                hasDataChanges = false;

                new CustomMessageBox(FormMessages.SaveSuccessMsg, FormMessages.SaveSuccessTitle, CustomMessageBoxButtons.OK).ShowDialog(this);
            }
            catch (Exception exp)
            {
                new CustomMessageBox(string.Format(FormMessages.ErrorSavingFile, exp.Message), FormMessages.ErrorMessageTitle, CustomMessageBoxButtons.Cancel).ShowDialog(this);
                return;
            }
        }

        /// <summary>
        /// Export to HTML or TXT
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnExport_Click(object sender, EventArgs e)
        {
            var file = new FileInfo(FilePath);
            if (file.Exists)
            {
                saveFileDialog1.InitialDirectory = file.DirectoryName;
                saveFileDialog1.FileName = "Export" + (new Regex("\\.vml$").Replace(file.Name, string.Empty));
            }

            if (saveFileDialog1.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    //create the file
                    var CreatedFile = new FileInfo(saveFileDialog1.FileName);
                    var fs = CreatedFile.Create();
                    var writer = new StreamWriter(fs);

                    if (CreatedFile.Extension.Equals(".html") || CreatedFile.Extension.Equals(".htm"))
                    {
                        #region Export to a HTML file

                        var sb = new StringBuilder();
                        foreach (CustomListViewItem item in lvVerbs.Items)
                        {
                            var verb = (Verb)item.AdditionalInfo[VocabularyVerbConst.Verb];
                            sb.AppendLine("<table class=\"tbl\">");
                            sb.AppendLine(
                                string.Format("<tr><td colspan=\"2\" class=\"infinitiveForm\">{0} = {1}</td></tr>",
                                              System.Web.HttpUtility.HtmlEncode(verb.InfinitiveForm),
                                              System.Web.HttpUtility.HtmlEncode(verb.Translation)));

                            if (!string.IsNullOrEmpty(verb.Examples.Trim()))
                                sb.AppendLine(string.Format("<tr><td>Examples:</td><td>{0}</t></tr>", NewLinesInHTML(verb.Examples)));
                            if (!string.IsNullOrEmpty(verb.Remarks.Trim()))
                                sb.AppendLine(string.Format("<tr><td>Remarks:</td><td>{0}</t></tr>", NewLinesInHTML(verb.Remarks)));
                            
                            foreach (var conjugation in verb.Conjugations)
                            {
                                sb.AppendLine(string.Format(
                                                  "<tr><td colspan=\"2\" class=\"conjugation\">{0}</td></tr>",
                                                  System.Web.HttpUtility.HtmlEncode(conjugation.Tense)));

                                sb.AppendLine("<tr><td colspan=\"2\">");
                                sb.AppendLine("<table class=\"noBorder\">");
                                foreach (var conjugationItem in conjugation.Conjugation)
                                {
                                    sb.AppendLine(
                                        string.Format(
                                            "<tr><td>{0}</td><td class=\"highlightedText\">{1}</td></tr>",
                                            System.Web.HttpUtility.HtmlEncode(conjugationItem.Key),
                                            System.Web.HttpUtility.HtmlEncode(conjugationItem.Value)));
                                }
                                sb.AppendLine("</table>");
                                sb.AppendLine("</td></tr>");

                                if (!string.IsNullOrEmpty(conjugation.Examples.Trim()))
                                    sb.AppendLine(string.Format("<tr><td>Examples:</td><td>{0}</t></tr>", NewLinesInHTML(conjugation.Examples)));
                                if (!string.IsNullOrEmpty(conjugation.Remarks.Trim()))
                                    sb.AppendLine(string.Format("<tr><td>Remarks:</td><td>{0}</t></tr>", NewLinesInHTML(conjugation.Remarks)));
                            }

                            sb.AppendLine("</table><br />");
                        }

                        writer.Write(FormMessages.HtmlFileStructure.Replace("{BODY_CONTENT}", sb.ToString()));

                        #endregion
                    }
                    else
                    {
                        #region Export to a TXT file

                        foreach (CustomListViewItem item in lvVerbs.Items)
                        {
                            var verb = (Verb)item.AdditionalInfo[VocabularyVerbConst.Verb];
                            writer.WriteLine("================================================");
                            writer.WriteLine("{0} = {1}", verb.InfinitiveForm, verb.Translation);
                            writer.WriteLine("================================================");
                            if (!string.IsNullOrEmpty(verb.Examples.Trim()))
                                WriteLinesToWriter(writer, string.Format("Examples : {0}", verb.Examples));
                            if (!string.IsNullOrEmpty(verb.Remarks.Trim()))
                                WriteLinesToWriter(writer, string.Format("Remarks : {0}", verb.Remarks));                                

                            foreach(var conjugation in verb.Conjugations)
                            {
                                writer.WriteLine("-----------------------------------------------");
                                writer.WriteLine(conjugation.Tense);
                                writer.WriteLine("-----------------------------------------------");
                                foreach (var conjugationItem in conjugation.Conjugation)
                                {
                                    writer.WriteLine("{0} {1}", conjugationItem.Key, conjugationItem.Value);
                                }
                                if (!string.IsNullOrEmpty(conjugation.Examples.Trim()))
                                    WriteLinesToWriter(writer, string.Format("Examples : {0}", conjugation.Examples));
                                if (!string.IsNullOrEmpty(conjugation.Remarks.Trim()))
                                    WriteLinesToWriter(writer, string.Format("Remarks : {0}", conjugation.Remarks));                               
                            }

                            writer.WriteLine(string.Empty);
                        }

                        #endregion
                    }

                    writer.Flush();
                    writer.Close();
                    fs.Close();
                }
                catch (Exception exp)
                {
                    (new CustomMessageBox(string.Format(FormMessages.ErrorExportingFile, exp.Message),
                                          FormMessages.ErrorMessageTitle, CustomMessageBoxButtons.OK)).ShowDialog(this);
                }
            }           
        }

        /// <summary>
        /// Make sure that \n or \r\n chars will appear as new lines
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="text"></param>
        protected static void WriteLinesToWriter(StreamWriter writer, string text)
        {
            var lines = (new Regex(@"\n|\r\n").Split(text));            
            foreach(var line in lines)
            {
                writer.WriteLine(line);
            }
        }

        /// <summary>
        /// Make sure that \n or \r\n chars will appear as new lines in HTML files
        /// </summary>
        /// <param name="text"></param>
        protected static string NewLinesInHTML(string text)
        {
           return (new Regex(@"\n|\r\n").Replace(System.Web.HttpUtility.HtmlEncode(text),"<br />"));            
        }

        private void btnReload_Click(object sender, EventArgs e)
        {
            if (hasDataChanges == false || (new CustomMessageBox(FormMessages.ConfirmReloadQuestion, FormMessages.ReloadFormTitle, CustomMessageBoxButtons.YesNo)).ShowDialog() == DialogResult.Yes)
            {
                LoadFile();
                LoadPersonsListView();
                LoadVerbsListView();
                hasDataChanges = false;
            }
        }

        private void btnTest_Click(object sender, EventArgs e)
        {
            if(lvVerbs.Items.Count == 0)
                return;

            var existingTenses = GetExistingTensesList();
            if (existingTenses.Count == 0)
            {
                StartTest(sender, true, existingTenses);
            }
            else
            {
                //Show the filters
                var form = new VerbTestFilter(GetExistingTensesList(), StartTest);
                form.ShowDialog(this);
            }
        }

        protected void StartTest(object sender, bool TestVerbsWithNoConjugation, List<string> VerbConjugations)
        {
            #region Denormalize the elements in the verbs list using an id

            var verbsList = new List<TestVerbAndConjugation>();
            var id = 1;
            foreach (CustomListViewItem item in lvVerbs.Items)
            {
                var verb = (Verb)item.AdditionalInfo[VocabularyVerbConst.Verb];
                var added = false;
                foreach (var conjugation in verb.Conjugations)
                {
                    if (VerbConjugations.Contains(conjugation.Tense))
                    {
                        verbsList.Add(new TestVerbAndConjugation
                                          {
                                              Id = id,
                                              InfinitiveForm = verb.InfinitiveForm,
                                              Translation = verb.Translation,
                                              Examples = verb.Examples,
                                              Remarks = verb.Remarks,
                                              Conjugation = conjugation
                                          });
                        added = true;
                    }
                }
                if (TestVerbsWithNoConjugation && !added)
                {
                    verbsList.Add(new TestVerbAndConjugation
                    {
                        Id = id,
                        InfinitiveForm = verb.InfinitiveForm,
                        Translation = verb.Translation,
                        Examples = verb.Examples,
                        Remarks = verb.Remarks,
                        Conjugation = null
                    });
                }
                id++;
            }

            if (verbsList.Count == 0)
            {
                (new CustomMessageBox(FormMessages.StartTestError, FormMessages.ErrorMessageTitle,
                                      CustomMessageBoxButtons.Cancel)).ShowDialog();
                return;
            }

            #endregion

            #region Get the grammatical persons list

            var persons = new List<Person>();
            foreach (CustomListViewItem item in lvPersons.Items)
            {
                var person = (Person)item.AdditionalInfo[VocabularyVerbConst.Person];
                persons.Add(person);
            }

            #endregion

            var form = new VerbTest(verbsList, FilePath, persons);
            form.ShowDialog(this);            
        }

        private void btnSort_Click(object sender, EventArgs e)
        {
            lvVerbs.Sorting = SortOrder.Ascending;
            lvVerbs.Sort();
            lvVerbs.Sorting = SortOrder.None;
        }

        private void btnSortDesc_Click(object sender, EventArgs e)
        {
            lvVerbs.Sorting = SortOrder.Descending;
            lvVerbs.Sort();
            lvVerbs.Sorting = SortOrder.None;
        }

        private void btnSearch_Click(object sender, EventArgs e)
        {
            lvVerbs.Focus();
            var firstItemFound = false;

            foreach (ListViewItem item in lvVerbs.Items)
            {
                if (!firstItemFound && (item.SubItems[0].Text.ToLower().Contains(tbSearch.Text.ToLower()) ||
                                        item.SubItems[1].Text.ToLower().Contains(tbSearch.Text.ToLower())))
                {
                    item.Selected = true;
                    item.EnsureVisible();
                    firstItemFound = true;
                }
                else
                    item.Selected = (item.SubItems[0].Text.ToLower().Contains(tbSearch.Text.ToLower()) ||
                                     item.SubItems[1].Text.ToLower().Contains(tbSearch.Text.ToLower()));
            }
        }

        private void tbSearch_DoubleClick(object sender, EventArgs e)
        {
            tbSearch.SelectAll();
        }

        private void tbSearch_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode != Keys.Enter)
                return;

            e.Handled = true;
            e.SuppressKeyPress = true;
            btnSearch_Click(sender, e);
        }

        #endregion
    }
}
